﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using MugenMvvmToolkit.Annotations;
using MugenMvvmToolkit.Attributes;
using MugenMvvmToolkit.Infrastructure;
using MugenMvvmToolkit.Interfaces.ViewModels;
using MugenMvvmToolkit.Models;

namespace MugenMvvmToolkit.ViewModels
{
    /// <summary>
    ///     Represents the base class for linear lists.
    /// </summary>
    /// <typeparam name="T">The type of model.</typeparam>
    [BaseViewModel(Priority = 7)]
    public class GridViewModel<T> : ViewModelBase, IGridViewModel<T> where T : class
    {
        #region Fields

        private readonly IList<T> _originalData;
        private FilterDelegate<T> _filter;
        private bool _isEmpty;
        private T _selectedItem;

        #endregion

        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="GridViewModel{T}" /> class.
        /// </summary>
        public GridViewModel()
        {
            _isEmpty = true;
            // ReSharper disable DoNotCallOverridableMethodsInConstructor
            _originalData = GetSourceCollection();
            FilterableItemsSource = new FilterableNotifiableCollection<T>(_originalData);
            CollectionChanged += OnCollectionChanged;
            CollectionChanging += OnCollectionChanging;
            // ReSharper restore DoNotCallOverridableMethodsInConstructor                        
        }

        #endregion

        #region Propreties

        /// <summary>
        ///     Gets the filterable items source.
        /// </summary>
        protected FilterableNotifiableCollection<T> FilterableItemsSource { get; private set; }

        #endregion

        #region Implementation of IGridViewModel

        /// <summary>
        ///     Gets the type of model.
        /// </summary>
        Type IGridViewModel.ModelType
        {
            get { return typeof(T); }
        }

        /// <summary>
        ///     Gets the original collection of items source without the filter.
        /// </summary>
        IEnumerable IGridViewModel.OriginalItemsSource
        {
            get { return OriginalItemsSource; }
        }

        /// <summary>
        ///     Gets the collection of objects.
        /// </summary>
        IEnumerable IGridViewModel.ItemsSource
        {
            get { return ItemsSource; }
        }

        /// <summary>
        ///     Gets or sets the selected item.
        /// </summary>
        object IGridViewModel.SelectedItem
        {
            get { return SelectedItem; }
            set { SelectedItem = (T)value; }
        }

        /// <summary>
        ///     Gets or sets the filter.
        /// </summary>
        FilterDelegate<object> IGridViewModel.Filter
        {
            set { Filter = value; }
        }

        /// <summary>
        ///     Gets or sets the filter.
        /// </summary>
        public virtual FilterDelegate<T> Filter
        {
            get { return _filter; }
            set
            {
                if (Equals(value, _filter)) return;
                _filter = value;
                UpdateFilter();
                OnPropertyChanged("Filter");
            }
        }

        /// <summary>
        ///     Updates the current <see cref="IGridViewModel.ItemsSource" />.
        /// </summary>
        /// <param name="value">The new item source value.</param>
        void IGridViewModel.UpdateItemsSource(IEnumerable value)
        {
            UpdateItemsSource((IEnumerable<T>)value);
        }

        /// <summary>
        ///     Occurs when the <c>SelectedItem</c> property changed.
        /// </summary>
        event SelectedItemChangedHandler<object> IGridViewModel.SelectedItemChanged
        {
            add { SelectedItemChanged += value; }
            remove { SelectedItemChanged -= value; }
        }

        /// <summary>
        ///     Occurs when the <c>ItemsSource</c> property changed.
        /// </summary>
        event ItemsSourceChangeHandler<IEnumerable> IGridViewModel.ItemsSourceChanged
        {
            add { ItemsSourceChanged += value; }
            remove { ItemsSourceChanged -= value; }
        }

        /// <summary>
        ///     Gets the original collection of items source without the filter.
        /// </summary>
        public virtual IList<T> OriginalItemsSource
        {
            get
            {
                if (_isEmpty)
                    return null;
                return FilterableItemsSource.SourceCollection;
            }
        }

        /// <summary>
        ///     Gets or sets the collection of objects.
        /// </summary>
        public virtual IList<T> ItemsSource
        {
            get
            {
                if (_isEmpty)
                    return null;
                return FilterableItemsSource;
            }
        }

        /// <summary>
        ///     Gets or sets the selected item.
        /// </summary>
        public virtual T SelectedItem
        {
            get { return _selectedItem; }
            set
            {
                if (Equals(_selectedItem, value)) return;
                T oldValue = _selectedItem;
                _selectedItem = OnSelectedItemChanging(value);
                if (Equals(_selectedItem, oldValue)) return;

                if (_selectedItem != null)
                {
                    FilterDelegate<T> filter = Filter;
                    if (filter != null && !filter(_selectedItem))
                        _selectedItem = null;
                }
#if WPF
                if (_selectedItem != null)
                {
                    value = _selectedItem;
                    ThreadManager.InvokeInUi(() =>
                                             {
                                                 if (ItemsSource == null) return;
                                                 ICollectionView collectionView =
                                                     System.Windows.Data.CollectionViewSource.GetDefaultView(ItemsSource);
                                                 if (collectionView == null) return;
                                                 collectionView.MoveCurrentTo(value);
                                             });
                }
#endif

                TrySubscribeToPropertyChanged(oldValue, false);
                TrySubscribeToPropertyChanged(_selectedItem, true);

                OnSelectedItemChanged(oldValue, _selectedItem);
                OnSelectedItemChangedEvent(oldValue, _selectedItem);
                OnPropertyChanged("SelectedItem");
            }
        }

        /// <summary>
        ///     Updates the current <see cref="IGridViewModel{T}.ItemsSource" />.
        /// </summary>
        /// <param name="value">The new items source value.</param>
        public void UpdateItemsSource(IEnumerable<T> value)
        {
            EnsureIsNotDisposed();
            UpdateItemsSourceInternal(value);
        }

        /// <summary>
        ///     Updates the filter state.
        /// </summary>
        public void UpdateFilter()
        {
            EnsureIsNotDisposed();
            UpdateFilterInternal();
        }

        /// <summary>
        ///     Occurs when the <c>SelectedItem</c> property changed.
        /// </summary>
        public virtual event SelectedItemChangedHandler<T> SelectedItemChanged;

        /// <summary>
        ///     Occurs when the <c>ItemsSource</c> property changed.
        /// </summary>
        public virtual event ItemsSourceChangeHandler<IEnumerable<T>> ItemsSourceChanged;

        #endregion

        #region Methods

        /// <summary>
        ///     Updates the current <see cref="IGridViewModel{T}.ItemsSource" />.
        /// </summary>
        /// <param name="value">The new items source value.</param>
        protected virtual void UpdateItemsSourceInternal(IEnumerable<T> value)
        {
            value = OnItemsSourceChanging(value);
            _isEmpty = value == null;
            SelectedItem = null;

            if (_isEmpty)
                _originalData.Clear();
            else
            {
                using (FilterableItemsSource.SuspendNotifications())
                {
                    _originalData.Clear();
                    _originalData.AddRange(value);
                }
            }
            UpdateFilter();
            OnItemsSourceChanged(value);
            OnItemsSourceChangedEvent(value);
            OnPropertyChanged("ItemsSource");
            OnPropertyChanged("OriginalItemsSource");
        }

        /// <summary>
        ///     Updates the filter state.
        /// </summary>
        protected virtual void UpdateFilterInternal()
        {
            if (_isEmpty) return;
            FilterDelegate<T> filter = Filter;
            if (filter == FilterableItemsSource.Filter)
                FilterableItemsSource.UpdateFilter();
            else
                FilterableItemsSource.Filter = filter;
            if (SelectedItem != null && filter != null && !filter(SelectedItem))
                SelectedItem = null;
        }

        /// <summary>
        ///     Gets the source collection of view model.
        /// </summary>
        /// <returns>An instance of collection.</returns>
        protected virtual IList<T> GetSourceCollection()
        {
            return new ObservableCollection<T>();
        }

        /// <summary>
        ///     Occurs when the <c>SelectedItem</c> property changing.
        /// </summary>
        /// <param name="newValue">The new value.</param>
        /// <returns>The value to set as selected item.</returns>
        protected virtual T OnSelectedItemChanging(T newValue)
        {
            return newValue;
        }

        /// <summary>
        ///     Occurs when the <c>SelectedItem</c> property changed.
        /// </summary>
        /// <param name="oldValue">The old value.</param>
        /// <param name="newValue">The new value.</param>
        protected virtual void OnSelectedItemChanged(T oldValue, T newValue)
        {
        }

        /// <summary>
        ///     Occurs when the <c>ItemsSource</c> property changing.
        /// </summary>
        /// <param name="data">The new item source data.</param>
        /// <returns>
        ///     An instance of <see cref="IEnumerable{T}" />.
        /// </returns>
        protected virtual IEnumerable<T> OnItemsSourceChanging(IEnumerable<T> data)
        {
            return data;
        }

        /// <summary>
        ///     Occurs when the <c>ItemsSource</c> property changed.
        /// </summary>
        /// <param name="data">The new item source data.</param>
        protected virtual void OnItemsSourceChanged(IEnumerable<T> data)
        {
        }

        /// <summary>
        ///     Invokes the <c>SelectedItemChanged</c> event.
        /// </summary>
        protected void OnSelectedItemChangedEvent(T old, T newRow)
        {
            SelectedItemChangedHandler<T> action = SelectedItemChanged;
            if (action == null) return;
            action(old, newRow);
        }

        /// <summary>
        ///     Invokes the event <c>ItemsSourceChanged</c>.
        /// </summary>
        protected void OnItemsSourceChangedEvent(IEnumerable<T> data)
        {
            ItemsSourceChangeHandler<IEnumerable<T>> handler = ItemsSourceChanged;
            if (handler == null) return;
            handler(data);
        }

        /// <summary>
        ///     Occurs when selected item property changed.
        /// </summary>
        protected virtual void OnSelectedItemPropertyChanged(object sender,
            PropertyChangedEventArgs propertyChangedEventArgs)
        {
        }

        /// <summary>
        ///     Occurs when collection changing.
        /// </summary>
        protected virtual void OnCollectionChanging(object sender,
            NotifyCollectionChangingEventArgs notifyCollectionChangingEventArgs)
        {
        }

        /// <summary>
        ///     Occurs when collection changed
        /// </summary>
        protected virtual void OnCollectionChanged(object sender,
            NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
        {
        }

        private void TrySubscribeToPropertyChanged(object item, bool isSubcribe)
        {
            var notifyPropertyChanged = item as INotifyPropertyChanged;
            if (notifyPropertyChanged == null) return;
            if (isSubcribe)
                notifyPropertyChanged.PropertyChanged += OnSelectedItemPropertyChanged;
            else
                notifyPropertyChanged.PropertyChanged -= OnSelectedItemPropertyChanged;
        }

        #endregion

        #region Overrides of ViewModelBase

        /// <summary>
        ///     Occurs after the initialization of the current <see cref="ViewModelBase" />.
        /// </summary>
        internal override void OnInitializedInternal()
        {
            if (ThreadManager != null)
                FilterableItemsSource.ThreadManager = ThreadManager;
            base.OnInitializedInternal();
        }

        /// <summary>
        ///     Occurs after current view model disposed, use for clear resource and event listeners(Internal only).
        /// </summary>
        internal override void OnDisposeInternal(bool disposing)
        {
            if (disposing)
            {
                CollectionChanged -= OnCollectionChanged;
                CollectionChanging -= OnCollectionChanging;
                SelectedItemChangedHandler<T> selectedItemChanged = SelectedItemChanged;
                if (selectedItemChanged != null)
                {
                    foreach (SelectedItemChangedHandler<T> @delegate in selectedItemChanged.GetInvocationList())
                        SelectedItemChanged -= @delegate;
                }
                ItemsSourceChangeHandler<IEnumerable<T>> itemsSourceChanged = ItemsSourceChanged;
                if (itemsSourceChanged != null)
                {
                    foreach (
                        ItemsSourceChangeHandler<IEnumerable<T>> @delegate in itemsSourceChanged.GetInvocationList())
                        ItemsSourceChanged -= @delegate;
                }
            }
            base.OnDisposeInternal(disposing);
        }

        #endregion

        #region Implementation of INotifyCollectionChanging

        /// <summary>
        ///     Occurs when the collection changes.
        /// </summary>
        public virtual event NotifyCollectionChangedEventHandler CollectionChanged
        {
            add { ((INotifyCollectionChanged)_originalData).CollectionChanged += value; }
            remove { ((INotifyCollectionChanged)_originalData).CollectionChanged -= value; }
        }

        /// <summary>
        ///     Occurs before the collection changes.
        /// </summary>
        public virtual event NotifyCollectionChangingEventHandler CollectionChanging
        {
            add { FilterableItemsSource.CollectionChanging += value; }
            remove { FilterableItemsSource.CollectionChanging -= value; }
        }

        #endregion
    }
}